using System;
using System.IO;
using System.Text;
using System.Threading;
using DZTC.UselessAppEver.Core;

namespace DZTC.UselessAppEver.Base32
{
    public class Base32Converter
    {
   
        private const string ENCODE_TABLE_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

        private const int QUANTUM_COUNT = 1024;

        protected const int SOURCE_BUFFER_LENGTH = SOURCE_QUANTUM_LENGTH * QUANTUM_COUNT;
        protected const int BASE32_BUFFER_LENGTH = BASE32_QUANTUM_LENGTH * QUANTUM_COUNT;

        protected const byte INVALID_CHARACTER = 0xff;
   
        private static byte[] CreateDecodeTable(byte[] encodeTable)
        {
            var result = new byte[256];

            for (int i = 0; i < result.Length; i++)
            {
                result[i] = INVALID_CHARACTER; 
            }

            for (byte i = 0; i < encodeTable.Length; i++)
            {
                result[encodeTable[i]] = i;
            }

            return result;
        }

        protected static readonly byte[] ENCODE_TABLE = Encoding.ASCII.GetBytes(ENCODE_TABLE_STR);
        protected static readonly byte[] DECODE_TABLE = CreateDecodeTable(ENCODE_TABLE);

        protected const byte PAD = (byte) '=';
        protected const byte CR = 0x0d;
        protected const byte LF = 0x0a;


        protected const int SOURCE_QUANTUM_LENGTH = 5;
        protected const int BASE32_QUANTUM_LENGTH = 8;




        public static void Encode(Stream input, Stream output,
                                  CancellationToken ct = default (CancellationToken),
                                  ProgressToken pt = null)
        {
            pt = pt ?? ProgressToken.None;

            if (input == null)
                throw new ArgumentNullException("input");

            if (output == null)
                throw new ArgumentException("output");

            if (!input.CanSeek)
                throw new ArgumentException("input stream must be seekable");

            if (!input.CanRead)
                throw new ArgumentException("Input stream must be readable");

            if (!output.CanWrite)
                throw new ArgumentException("Output stream must be writeable");

            //output.Seek(GetDestinationLength(input.Length), SeekOrigin.Begin);

            Encoder.EncodeInernal(input, output, ct, pt);

            // finaly, flush the output
            output.Flush();
        }

        public static  string EncodeString(string str, Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.ASCII;

            using (var input = new MemoryStream(encoding.GetBytes(str)))
            using (var output = new MemoryStream())
            {
                Encoder.EncodeInernal(input, output, CancellationToken.None, ProgressToken.None);

                return encoding.GetString(output.GetBuffer(), 0, (int) output.Length);
            }
        }


        public static void Decode(Stream input, Stream output, CancellationToken ct = default(CancellationToken),
                                    ProgressToken pt = null)
        {
            pt = pt ?? ProgressToken.None;

            if (input == null)
                throw new ArgumentNullException("input");

            if (output == null)
                throw new ArgumentException("output");

            if (!input.CanSeek)
                throw new ArgumentException("input stream must be seekable");

            if (!input.CanRead)
                throw new ArgumentException("Input stream must be readable");

            if (!output.CanWrite)
                throw new ArgumentException("Output stream must be writeable");

            Decoder.DecodeInternal(input, output, ct, pt);

            output.Flush();
        }

        public static string DecodeString(string str, Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.ASCII;

            using (var input = new MemoryStream(encoding.GetBytes(str)))
            using (var output = new MemoryStream())
            {
                Decoder.DecodeInternal(input, output, CancellationToken.None, ProgressToken.None);

                return encoding.GetString(output.GetBuffer(), 0, (int)output.Length);
            }
        }
    }

}